package com.todo.strategy.online.application.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.todo.common.common.constants.BizConstants;
import com.todo.common.common.entity.response.page.PageResult;
import com.todo.common.common.enums.BizErrorCodeEnum;
import com.todo.common.common.exception.BizException;
import com.todo.common.utils.DateUtils;
import com.todo.component.redis.RedisService;
import com.todo.strategy.common.constant.StrategyOnlineKeyBuild;
import com.todo.strategy.common.enums.PlanRelationSceneEnum;
import com.todo.strategy.online.application.convert.StrategyConvert;
import com.todo.strategy.online.application.entity.bo.PlanRelationBO;
import com.todo.strategy.online.application.entity.bo.PlanSetBO;
import com.todo.strategy.online.application.entity.condition.PlanSetConditionBO;
import com.todo.strategy.online.application.service.PlanRelationService;
import com.todo.strategy.online.application.service.PlanSetService;
import com.todo.strategy.online.infrastructure.entity.condition.PlanSetCondition;
import com.todo.strategy.online.infrastructure.entity.po.PlanSetPO;
import com.todo.strategy.online.infrastructure.repository.PlanSetRepository;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * (PlanSet)表服务实现类
 *
 * @author zhangwenxiang
 * @since 2024-03-04 00:19:47
 */
@Slf4j
@Service("planSetService")
public class PlanSetServiceImpl implements PlanSetService {

    public static final int DEFAULT_QUERY_INTERVAL_DAY = 90;
    private static final int DEFAULT_QUERY_PLAN_SET_SIZE = 100;

    @Resource
    private PlanSetRepository planSetRepository;
    @Resource
    private PlanRelationService planRelationService;
    @Resource
    private RedisService redisService;

    @Override
    public Boolean insertPlanSet(PlanSetBO planSetBO) {
        if (planSetBO == null) {
            throw new BizException(BizErrorCodeEnum.PARAM_ERROR);
        }
        if (planSetBO.getStartPlanTime() == null || planSetBO.getEndPlanTime() == null || planSetBO.getStartPlanTime().after(planSetBO.getEndPlanTime())) {
            throw new BizException(BizErrorCodeEnum.PARAM_ERROR, "开始结束时间异常");
        }
        //计算相差天数，超过15天不做保存
        int diffDay = DateUtils.differentDaysByMillisecond(planSetBO.getStartPlanTime(), planSetBO.getEndPlanTime());
        if (diffDay > BizConstants.DEFAULT_MAX_PLAN_SET_DIFF_DAY) {
            log.warn("PlanSetService#insertPlanSet 保存清单计划失败，旅游期间间隔过大，建议做小区间计划 planSetBO：【{}】", JSONObject.toJSONString(planSetBO));
            throw new BizException(BizErrorCodeEnum.BASE_BUSINESS_ERROR, "旅游期间间隔过大，建议做小区间计划，避免后续调整复杂");
        }
        PlanSetPO sameNamePlanSetPO = planSetRepository.queryPlanSetByUserAndTitle(planSetBO.getUserId(), planSetBO.getTitle());
        if (sameNamePlanSetPO != null) {
            log.warn("PlanSetService#insertPlanSet 保存清单计划失败，已存在同名的清单计划 planSetBO：【{}】", JSONObject.toJSONString(planSetBO));
            throw new BizException(BizErrorCodeEnum.BASE_BUSINESS_ERROR, "已存在同名的清单计划");
        }
        //计算当前用户已保存的清单计划数量
        PlanSetCondition planSetCondition = new PlanSetCondition();
        planSetCondition.setUserId(planSetBO.getUserId());
        Long currentCount = planSetRepository.queryCountByCondition(planSetCondition);
        if (currentCount >= BizConstants.DEFAULT_MAX_PLAN_SET_SIZE) {
            throw new BizException(BizErrorCodeEnum.QUANTITY_EXPLOSION, "创建清单数量已经超限");
        }
        PlanSetPO planSetPO = StrategyConvert.convertToPlanSetPO(planSetBO);
        Boolean saveResult = planSetRepository.insertPlanSetPO(planSetPO);
        if (saveResult) {
            String detailKey = StrategyOnlineKeyBuild.planSetDetailKey(planSetBO.getPlanId());
            redisService.set8H(detailKey, JSONObject.toJSONString(planSetBO));
        }
        log.info("PlanSetService#insertPlanSet 保存清单计划完成，planSetBO：【{}】,结果：【{}】", JSONObject.toJSONString(planSetBO),saveResult);
        return saveResult;
    }

    @Override
    public Boolean updatePlanSet(PlanSetBO planSetBO) {
        if (planSetBO == null || planSetBO.getPlanId() == null) {
            throw new BizException(BizErrorCodeEnum.PARAM_ERROR);
        }
        PlanSetBO alreadyPlanSetBO = queryPlanSetById(planSetBO.getPlanId());
        if (alreadyPlanSetBO == null) {
            log.warn("PlanSetService#updatePlanSet 更新清单计划为空，根据主键查询数据为空，默认更新失败 planSetBO：【{}】", JSONObject.toJSONString(planSetBO));
            throw new BizException(BizErrorCodeEnum.DATA_NOT_EXIST);
        }
        if (planSetBO.getStartPlanTime() == null || planSetBO.getEndPlanTime() == null || planSetBO.getStartPlanTime().after(planSetBO.getEndPlanTime())) {
            throw new BizException(BizErrorCodeEnum.PARAM_ERROR, "开始结束时间异常");
        }
        //计算相差天数，超过15天不做保存
        int diffDay = DateUtils.differentDaysByMillisecond(planSetBO.getStartPlanTime(), planSetBO.getEndPlanTime());
        if (diffDay > BizConstants.DEFAULT_MAX_PLAN_SET_DIFF_DAY) {
            log.warn("PlanSetService#updatePlanSet 更新清单计划失败，旅游期间间隔过大，建议做小区间计划 planSetBO：【{}】", JSONObject.toJSONString(planSetBO));
            throw new BizException(BizErrorCodeEnum.BASE_BUSINESS_ERROR, "旅游期间间隔过大，建议做小区间计划");
        }
        //标题替换情况下验证
        if (!alreadyPlanSetBO.getTitle().equals(planSetBO.getTitle())) {
            PlanSetPO sameNamePlanSetPO = planSetRepository.queryPlanSetByUserAndTitle(planSetBO.getUserId(), planSetBO.getTitle());
            if (sameNamePlanSetPO != null) {
                log.warn("PlanSetService#updatePlanSet 更新清单计划失败，已存在同名的清单计划 planSetBO：【{}】", JSONObject.toJSONString(planSetBO));
                throw new BizException(BizErrorCodeEnum.BASE_BUSINESS_ERROR, "已存在同名的清单计划");
            }
        }
        List<PlanRelationBO> planRelationBOS = planRelationService.queryPlanRelationByBizId(planSetBO.getPlanId(), PlanRelationSceneEnum.USER_STRATEGY);
        if (CollectionUtils.isNotEmpty(planRelationBOS)) {
            planSetBO.setRelationCount(planRelationBOS.size());
        }

        PlanSetPO planSetPO = StrategyConvert.convertToPlanSetPO(planSetBO);
        Boolean updateResult = planSetRepository.updatePlanSetPOById(planSetPO);
        if (updateResult) {
            String detailKey = StrategyOnlineKeyBuild.planSetDetailKey(planSetBO.getPlanId());
            redisService.del(detailKey);
        }
        log.info("PlanSetService#updatePlanSet 更新清单计划完成，planSetBO：【{}】,结果：【{}】", JSONObject.toJSONString(planSetBO),updateResult);
        return updateResult;
    }

    @Override
    @Transactional
    public Boolean removePlanSetById(Long id) {
        if (id == null || id <= 0) {
            throw new BizException(BizErrorCodeEnum.PARAM_ERROR);
        }
        PlanSetBO planSetBO = queryPlanSetById(id);
        if (planSetBO == null) {
            log.info("PlanSetService#removePlanSetById 根据主键删除计划清单完成 ，根据主键查询数据为空，默认删除成功 id:【{}】", id);
            return true;
        }
        Boolean removeResult = planSetRepository.removePlanSetPOById(id);
        if (removeResult) {

            Boolean removed = planRelationService.removePlanRelationByBizId(id, PlanRelationSceneEnum.USER_STRATEGY);
            if (removed) {
                String detailKey = StrategyOnlineKeyBuild.planSetDetailKey(id);
                redisService.del(detailKey);
            }
        }
        log.info("PlanSetService#removePlanSetById 根据主键删除计划清单完成 ，id:【{}】,结果：【{}】", id,removeResult);
        return removeResult;
    }

    @Override
    public PlanSetBO queryPlanSetById(Long id) {
        if (id == null || id < 0) {
            return null;
        }
        String detailKey = StrategyOnlineKeyBuild.planSetDetailKey(id);
        if (redisService.hasKey(detailKey)) {
            PlanSetBO planSetBO = JSONObject.parseObject(redisService.get(detailKey), PlanSetBO.class);
            return planSetBO;
        }
        PlanSetPO planSetPO = planSetRepository.queryPlanSetPOById(id);
        PlanSetBO planSetBO = StrategyConvert.convertToPlanSetBO(planSetPO);
        if (planSetBO != null) {
            redisService.set8H(detailKey, JSONObject.toJSONString(planSetBO));
        }
        log.info("PlanSetService#queryPlanSetById 数据库根据主键查询计划清单完成 ，id:【{}】,结果：【{}】", id,planSetBO);
        return planSetBO;
    }

    @Override
    public List<PlanSetBO> queryPlanSetByUserId(Long userId) {
        if (userId == null || userId <= 0) {
            return Collections.emptyList();
        }
        List<PlanSetPO> planSetPOList = planSetRepository.queryPlanSetPOByUserId(userId);
        if (planSetPOList == null) {
            log.warn("PlanSetService#queryPlanSetByUserId 根据用户id查询计划清单完成 ，根据用户id查询数据为空 userId:【{}】", userId);
            return Collections.emptyList();
        }
        return planSetPOList.stream().map(StrategyConvert::convertToPlanSetBO).collect(Collectors.toList());
    }

    @Override
    public PageResult<PlanSetBO> queryPlanSetByCondition(PlanSetConditionBO planSetConditionBO) {
        if (planSetConditionBO == null) {
            return new PageResult<>();
        }
        PlanSetCondition planSetCondition = StrategyConvert.convertToPlanSetCondition(planSetConditionBO);
        PageResult<PlanSetPO> pageResult = planSetRepository.queryPlanSetPOByCondition(planSetCondition);
        if (pageResult == null) {
            return new PageResult<>();
        }
        PageResult<PlanSetBO> result = new PageResult<>();
        result.setResults(pageResult.getResults() == null ? null : pageResult.getResults().stream().map(StrategyConvert::convertToPlanSetBO).collect(Collectors.toList()));
        result.setHasNext(pageResult.isHasNext());
        result.setTotal(pageResult.getTotal());
        result.setPageCount(pageResult.getPageCount());
        result.setPageNum(pageResult.getPageNum());
        result.setCurrentIndex(pageResult.getCurrentIndex());
        result.setPageSize(pageResult.getPageSize());
        return result;
    }

    @Override
    @Transactional
    public Boolean removePlanSetByIdList(Long userId, List<String> planIdList) {
        if (userId == null || userId <= 0) {
            throw new BizException(BizErrorCodeEnum.PARAM_ERROR);
        }
        if (CollectionUtils.isEmpty(planIdList)) {
            return true;
        }
        for (String planId : planIdList) {
            PlanSetBO planSetBO = queryPlanSetById(Long.valueOf(planId));
            if (planSetBO == null) {
                continue;
            }
            if (!planSetBO.getUserId().equals(userId)) {
                throw new BizException(BizErrorCodeEnum.INSUFFICIENT_USER_PERMISSIONS, "无法删除非本人数据");
            }
            removePlanSetById(planSetBO.getPlanId());
        }
        return true;
    }

    @Override
    public List<PlanSetBO> queryPlanSetListByTime(Long userId, LocalDateTime startTime, LocalDateTime endTime) {
        if (userId == null || userId <= 0 || startTime == null || endTime == null) {
            throw new BizException(BizErrorCodeEnum.PARAM_ERROR);
        }
        long daysBetween = ChronoUnit.DAYS.between(startTime, endTime);

        if (startTime.isAfter(endTime) || daysBetween > DEFAULT_QUERY_INTERVAL_DAY) {
            throw new BizException(BizErrorCodeEnum.INTERVAL_DAYS_ERROR, "查询时间范围过大，请重新选择");
        }
        List<PlanSetPO> planSetPOList = planSetRepository.queryPlanSetListByTime(userId, startTime, endTime);
        if (planSetPOList == null) {
            return Collections.emptyList();
        }
        return planSetPOList.stream().map(StrategyConvert::convertToPlanSetBO).collect(Collectors.toList());
    }

    @Override
    public List<PlanSetBO> queryPlanSetByIdList(List<Long> planIdList) {
        if (CollectionUtils.isEmpty(planIdList)) {
            return Collections.emptyList();
        }
        if (planIdList.size() > DEFAULT_QUERY_PLAN_SET_SIZE) {
            throw new BizException(BizErrorCodeEnum.QUANTITY_EXPLOSION, "单次查询数量过大");
        }
        List<PlanSetBO> result = new ArrayList<>();
        for (Long planId : planIdList) {
            PlanSetBO planSetBO = queryPlanSetById(planId);
            if (planSetBO != null) {
                result.add(planSetBO);
            }
        }
        return result;
    }
}
